﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ComputerBeacon.Csv {
    /// <summary>
    /// A comma-separated values scanner implementing the RFC 4180 standard
    /// </summary>
    class Scanner {
        string s;
        int i = 0; //marks the position in the string
        StringBuilder sb = new StringBuilder();
        string temp = null;
        public Scanner(string s) {
            this.s = s;
        }
  
        /// <summary>
        /// Advances the scanner to the next token in the string
        /// </summary>
        /// <returns></returns>
        public Token Next() {
            if (i == -1) throw new InvalidOperationException("End of string already reached");

            //note: state is not carried forward between tokens
            byte state = 0;

            while (i < s.Length) {
                switch (state) {
                    //begin state
                    case 0:
                        switch (s[i]) {
                            case ',':
                                i++;
                                temp = sb.ToString();
                                sb.Length = 0;
                                return new Token { NextType = TokenType.String, Value = temp };
                            case '\r':
                                i++;
                                state = 1;
                                break;
                            case '\n':
                                state = 1;
                                break;
                            case '"':
                                i++;
                                state = 2;
                                break;
                            default:
                                sb.Append(s[i++]);
                                break;
                        }
                        break;

                    //expecting a new line
                    case 1:
                        if (s[i++] != '\n') throw new FormatException("'\\r' character must be followed by '\\n'");
                        temp = sb.ToString();
                        sb.Length = 0;
                        return new Token { NextType = TokenType.Newline, Value = temp };

                    //inside quoted string
                    case 2:
                        if (s[i] == '"') state = 3;
                        else sb.Append(s[i]);
                        ++i;
                        break;

                    //quote inside quoted string
                    case 3:
                        switch (s[i++]) {
                            case '"':
                                sb.Append('"');
                                state = 2;
                                break;
                            case ',':
                                temp = sb.ToString();
                                sb.Length = 0;
                                return new Token { NextType = TokenType.String, Value = temp };
                            case '\r':
                                state = 1;
                                break;
                            case '\n':
                                --i; //roll back, do not eat the \n character
                                state = 1;
                                break;
                            default:
                                throw new FormatException("Terminating quote must be followed by comma, newline or end of string");
                        }
                        break;
                }
            }

            if (state == 2) throw new FormatException("Incomplete quoted string. Terminating quote is not reached before end of string.");

            i = -1; //to indicate that the iteration has finished
            return new Token { NextType = TokenType.End, Value = sb.ToString() };
        }

        public struct Token {
            /// <summary>
            /// What follows after the string value
            /// </summary>
            public TokenType NextType;
            public string Value;
            public override string ToString() {
                return "Type=" + NextType.ToString() + ", Value=" + Value;
            }
        }

        public enum TokenType {
            End,
            String,
            Newline
        }
    }
}
